package com.czx.util;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * @author topaa
 * @date 2020/9/23 12:43
 */
public class CustomProxy {

    private static final String CODE = "F:\\code\\";
    private static final String PROXY = "$proxy";

    public static Object newProxyInstance(Class inf, InvocationHandler h) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        // 生成java文件
        String javaFile = createJavaFile(inf);
        // 编译class
        compiler(javaFile);
        // 加载到jvm
        Class clazz = loadClass();
        // 初始化
        Object o = initialize(clazz, h);
        return o;
    }

    private static String createJavaFile(Class inf) throws IOException {
        String infName = inf.getSimpleName();
        String infAllName = inf.getName();

        StringBuilder sb = new StringBuilder();
        sb.append("import " + infAllName + ";")
                .append("import " + InvocationHandler.class.getName() + ";")
                .append("import java.lang.reflect.Method;")
                .append("import java.lang.reflect.UndeclaredThrowableException;")
                .append("public class " + PROXY + " implements " + infName + " {")
                .append("private InvocationHandler h;")
                .append("public " + PROXY + "(InvocationHandler h){")
                .append("this.h = h;")
                .append("}");

        Method[] declaredMethods = inf.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            String returnType = "";
            String returnTypeName = declaredMethod.getReturnType().getSimpleName();
            if (!"void".equals(returnTypeName)) {
                returnType = "return (" + returnTypeName + ")";
            }
            StringBuilder params = new StringBuilder();
            StringBuilder paramsClass = new StringBuilder();
            StringBuilder args = new StringBuilder();
            Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++) {
                if (params.length() > 0) {
                    params.append("," + parameterTypes[i].getSimpleName() + " var" + i);
                    paramsClass.append("," + parameterTypes[i].getSimpleName() + ".class");
                    args.append(", " + " var" + i);
                } else {
                    params.append(parameterTypes[i].getSimpleName() + " var" + i);
                    paramsClass.append(parameterTypes[i].getSimpleName() + ".class");
                    args.append(" var" + i);
                }
            }

            sb.append("public " + returnTypeName + " " + declaredMethod.getName() + "(" + params.toString() + ") {")
                    .append("try {")
                    .append("Class<?> clazz = Class.forName(\"" + infAllName + "\");")
                    .append("Class[] argsClass = new Class[]{" + paramsClass.toString() + "};")
                    .append("Method method = clazz.getMethod(\"" + declaredMethod.getName() + "\", argsClass);")
                    .append("Object[] args = new Object[]{" + args.toString() + "};")
                    .append(returnType + "h.invoke(this, method, args);")
                    .append("} catch (Throwable throwable) {")
                    .append("throw new UndeclaredThrowableException(throwable);");
            sb.append("}")
                    .append("}");
        }
        sb.append("}");

        String fileName = PROXY + ".java";
        File file = new File(CODE + fileName);
        FileWriter fileWriter = new FileWriter(file);
        fileWriter.write(sb.toString());
        fileWriter.flush();
        fileWriter.close();
        return fileName;
    }

    private static void compiler(String fileName) {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        compiler.run(null, null, null, CODE + fileName);
    }

    private static Class loadClass() throws MalformedURLException, ClassNotFoundException {
        URL url = new URL("file:" + CODE);
        ClassLoader loader = new URLClassLoader(new URL[]{url});
        return loader.loadClass(PROXY);
    }

    private static Object initialize(Class clazz, InvocationHandler h) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor constructor = clazz.getConstructor(InvocationHandler.class);
        Object o = constructor.newInstance(h);
        return o;
    }
}
